/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.parsing;

import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.ibatis.builder.BuilderException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * @author Clinton Begin
 */
/**
 * XPath解析器，用的都是JDK的类包,封装了一下，使得使用起来更方便
 *
 */
public class XPathParser {
	/**
	 * 用来解析xml文件的 包括检测xml文件格式
	 */
	private Document document;
	/**
	 * 验证
	 */
	private boolean validation;
	/**
	 * 通过key查找dtd文件
	 */
	private EntityResolver entityResolver;
	/**
	 * 变量
	 */
	private Properties variables;
	/**
	 * 将元素转换成为节点信息
	 */
	private XPath xpath;

	// 一些构造函数,全部调用commonConstructor以及createDocument
	// 1~4,默认不需要验证
	/**
	 * 通过xml去构造XPathParser对象
	 * 
	 * @param xml
	 */
	public XPathParser(String xml) {
		commonConstructor(false, null, null);
		this.document = createDocument(new InputSource(new StringReader(xml)));
	}

	/**
	 * 通过reader去构造XPathParser对象
	 * 
	 * @param reader
	 *            操作字符流的类
	 */
	public XPathParser(Reader reader) {
		commonConstructor(false, null, null);
		this.document = createDocument(new InputSource(reader));
	}
	/**
	 * 通过inputStream去构造XPathParser对象
	 * @param inputStream 操作字节流的类
	 */
	public XPathParser(InputStream inputStream) {
		commonConstructor(false, null, null);
		this.document = createDocument(new InputSource(inputStream));
	}
	/**
	 * 通过document去构造XPathParser对象
	 * @param document this.document
	 */
	public XPathParser(Document document) {
		commonConstructor(false, null, null);
		this.document = document;
	}
	/**
	 * 通过XML和validation属性去构造XPathParser对象,
	 * @param xml
	 * @param validation 验证
	 */
	// 5~8,传入是否需要验证参数
	public XPathParser(String xml, boolean validation) {
		commonConstructor(validation, null, null);
		this.document = createDocument(new InputSource(new StringReader(xml)));
	}
	/**
	 * 通过reader和validation属性去构造XPathParser对象,
	 * @param reader 操作字符流的类
	 * @param validation 验证
	 */
	public XPathParser(Reader reader, boolean validation) {
		commonConstructor(validation, null, null);
		this.document = createDocument(new InputSource(reader));
	}
	/**
	 * 通过inputStream和validation属性去构造XPathParser对象,
	 * @param inputStream 操作字节流的类
	 * @param validation 验证
	 */
	public XPathParser(InputStream inputStream, boolean validation) {
		commonConstructor(validation, null, null);
		this.document = createDocument(new InputSource(inputStream));
	}
	/**
	 * 通过document和validation属性去构造XPathParser对象,
	 * @param document  this.document
	 * @param validation 验证
	 */
	public XPathParser(Document document, boolean validation) {
		commonConstructor(validation, null, null);
		this.document = document;
	}
	/**
	 * 通过document和validation属性和variables变量去构造XPathParser对象,
	 * @param xml
	 * @param validation 验证
	 * @param variables 变量
	 */
	// 9~12,传入是否需要验证参数,Properties
	public XPathParser(String xml, boolean validation, Properties variables) {
		commonConstructor(validation, variables, null);
		this.document = createDocument(new InputSource(new StringReader(xml)));
	}
	/**
	 * 通过reader和validation属性和variables变量去构造XPathParser对象,
	 * @param reader 操作字符流的类
	 * @param validation 验证
	 * @param variables 变量
	 */
	public XPathParser(Reader reader, boolean validation, Properties variables) {
		commonConstructor(validation, variables, null);
		this.document = createDocument(new InputSource(reader));
	}
	/**
	 * 通过inputStream和validation属性和variables变量去构造XPathParser对象,
	 * @param inputStream 操作字节流的类
	 * @param validation 验证
	 * @param variables 变量
	 */
	public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
		commonConstructor(validation, variables, null);
		this.document = createDocument(new InputSource(inputStream));
	}
	/**
	 * 通过document和validation属性和variables变量去构造XPathParser对象,
	 * @param document this.document
	 * @param validation  验证
	 * @param variables   变量
	 */
	public XPathParser(Document document, boolean validation, Properties variables) {
		commonConstructor(validation, variables, null);
		this.document = document;
	}
	/**
	 * 通过xml和validation属性和variables变量和entityResolver(实体解析器)去构造XPathParser对象,
	 * @param xml 
	 * @param validation 验证
	 * @param variables  变量
	 * @param entityResolver 实体解析器
	 */
	// 13~16,传入是否需要验证参数,Properties,EntityResolver
	public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {
		commonConstructor(validation, variables, entityResolver);
		this.document = createDocument(new InputSource(new StringReader(xml)));
	}
	/**
	 * 通过reader和validation属性和variables变量和entityResolver(实体解析器)去构造XPathParser对象,
	 * @param reader 操作字符流的类
	 * @param validation 验证
	 * @param variables 变量
	 * @param entityResolver 实体解析器
	 */
	public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
		commonConstructor(validation, variables, entityResolver);
		this.document = createDocument(new InputSource(reader));
	}
	/**
	 * 通过inputStream和validation属性和variables变量和entityResolver(实体解析器)去构造XPathParser对象,
	 * @param inputStream 操作字节流的类
	 * @param validation 验证
	 * @param variables 变量
	 * @param entityResolver 实体解析器
	 */
	public XPathParser(InputStream inputStream, boolean validation, Properties variables,
			EntityResolver entityResolver) {
		commonConstructor(validation, variables, entityResolver);
		this.document = createDocument(new InputSource(inputStream));
	}
	/**
	 * 通过document和validation属性和variables变量和entityResolver(实体解析器)去构造XPathParser对象,
	 * @param document this.document
	 * @param validation 验证 
	 * @param variables 变量
	 * @param entityResolver 实体解析器
	 */
	public XPathParser(Document document, boolean validation, Properties variables, EntityResolver entityResolver) {
		commonConstructor(validation, variables, entityResolver);
		this.document = document;
	}

	// 17.设置Properties
	/**
	 * 设置Properties
	 * @param variables 变量
	 */
	public void setVariables(Properties variables) {
		this.variables = variables;
	}
	/**
	 * 解析String类型
	 * @param expression 表达式
	 * @return String
	 */
	public String evalString(String expression) {
		return evalString(document, expression);
	}
	/**
	 * 解析String类型--先解析文件，后解析参数
	 * @param root this.document
	 * @param expression 表达式
	 * @return String
	 */
	public String evalString(Object root, String expression) {
		// 1.先用xpath解析
		String result = (String) evaluate(expression, root, XPathConstants.STRING);
		// 2.再调用PropertyParser去解析,也就是替换 ${} 这种格式的字符串
		result = PropertyParser.parse(result, variables);
		return result;
	}
	/**
	 * 解析boolean类型
	 * @param expression 表达式
	 * @return Boolean
	 */
	public Boolean evalBoolean(String expression) {
		return evalBoolean(document, expression);
	}
	/**
	 * 解析boolean类型
	 * @param root this.document
	 * @param expression 表达式
	 * @return Boolean
	 */
	public Boolean evalBoolean(Object root, String expression) {
		return (Boolean) evaluate(expression, root, XPathConstants.BOOLEAN);
	}
	/**
	 * 解析Short类型
	 * @param expression 表达式
	 * @return Short
	 */ 
	public Short evalShort(String expression) {
		return evalShort(document, expression);
	}
	/**
	 * 解析Short类型
	 * @param root this.document
	 * @param expression 表达式
	 * @return Short
	 */
	public Short evalShort(Object root, String expression) {
		return Short.valueOf(evalString(root, expression));
	}
	/**
	 * 解析Integer类型
	 * @param expression 表达式
	 * @return Integer
	 */
	public Integer evalInteger(String expression) {
		return evalInteger(document, expression);
	}
	/**
	 * 解析Integer类型
	 * @param root this.document
	 * @param expression 表达式
	 * @return Integer
	 */ 
	public Integer evalInteger(Object root, String expression) {
		return Integer.valueOf(evalString(root, expression));
	}
	/**
	 * 解析Long类型
	 * @param expression 表达式
	 * @return Long
	 */
	public Long evalLong(String expression) {
		return evalLong(document, expression);
	}
	/**
	 * 解析Long类型
	 * @param root this.document
	 * @param expression 表达式
	 * @return Long
	 */
	public Long evalLong(Object root, String expression) {
		return Long.valueOf(evalString(root, expression));
	}
	/**
	 * 解析Float类型
	 * @param expression 表达式
	 * @return Float
	 */
	public Float evalFloat(String expression) {
		return evalFloat(document, expression);
	}

	// ??这里有点疑问，为何Float用evalString,Double用evaluate XPathConstants.NUMBER
	/**
	 * 解析Float类型
	 * @param root this.document
	 * @param expression 表达式
	 * @return Float
	 */
	public Float evalFloat(Object root, String expression) {
		return Float.valueOf(evalString(root, expression));
	}
	/**
	 * 解析Double类型
	 * @param expression 表达式
	 * @return Double
	 */
	public Double evalDouble(String expression) {
		return evalDouble(document, expression);
	}
	/**
	 * 解析Double类型
	 * @param root this.document
	 * @param expression 表达式
	 * @return Double
	 */
	public Double evalDouble(Object root, String expression) {
		return (Double) evaluate(expression, root, XPathConstants.NUMBER);
	}
	/**
	 * 解析List<xml节点>集合
	 * @param expression 表达式
	 * @return List<XNode>
	 */
	public List<XNode> evalNodes(String expression) {
		return evalNodes(document, expression);
	}

	// 返回节点List
	/**
	 * 解析List<xml节点>集合
	 * @param root this.document
	 * @param expression 表达式
	 * @return List<XNode>
	 */
	public List<XNode> evalNodes(Object root, String expression) {
		List<XNode> xnodes = new ArrayList<XNode>();
		NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
		for (int i = 0; i < nodes.getLength(); i++) {
			xnodes.add(new XNode(this, nodes.item(i), variables));
		}
		return xnodes;
	}
    /**
     * 解析xml节点 --根节点
     * @param expression 表达式
     * @return XNode
     */
	public XNode evalNode(String expression) {
		return evalNode(document, expression);
	}

	// 返回节点
	 /**
     * 解析xml节点  --根节点
     * @param root this.document
     * @param expression 表达式
     * @return XNode
     */
	public XNode evalNode(Object root, String expression) {
		Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
		if (node == null) {
			return null;
		}
		return new XNode(this, node, variables);
	}
	/**
	 * 解析 
	 * @param expression 表达式
	 * @param root this.document
	 * @param returnType 返回的类型
	 * @return this.document
	 */
	private Object evaluate(String expression, Object root, QName returnType) {
		try {
			// 最终合流到这儿，直接调用XPath.evaluate
			//调用xpath类进行相应的解析。
		    //注意returnType参数，虽然evaluate返回的数据类型是Object的，但是如果指定了错误的returnType，那么在进行类型转换时将会报类型转换异常
			return xpath.evaluate(expression, root, returnType);
		} catch (Exception e) {
			throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
		}
	}
	/**
	* 将xml数据源解析成Document对象
	* @param inputSource 操作源(文档<xml...>) 能够解析外部实体以及其它特定于文档来源的资源
	* @return Document
	*/
	private Document createDocument(InputSource inputSource) {
		// important: this must only be called AFTER common constructor
		try {
			// 这个是DOM解析方式
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setValidating(validation);

			// 名称空间
			factory.setNamespaceAware(false);
			// 忽略注释
			factory.setIgnoringComments(true);
			// 忽略空白
			factory.setIgnoringElementContentWhitespace(false);
			// 把 CDATA 节点转换为 Text 节点
			factory.setCoalescing(false);
			// 扩展实体引用
			factory.setExpandEntityReferences(true);

			DocumentBuilder builder = factory.newDocumentBuilder();
			// 需要注意的就是定义了EntityResolver(XMLMapperEntityResolver)，这样不用联网去获取DTD，
			// 将DTD放在org\apache\ibatis\builder\xml\mybatis-3-config.dtd,来达到验证xml合法性的目的
			builder.setEntityResolver(entityResolver);
			builder.setErrorHandler(new ErrorHandler() {
				@Override
				public void error(SAXParseException exception) throws SAXException {
					throw exception;
				}

				@Override
				public void fatalError(SAXParseException exception) throws SAXException {
					throw exception;
				}

				@Override
				public void warning(SAXParseException exception) throws SAXException {
				}
			});
			return builder.parse(inputSource);
		} catch (Exception e) {
			throw new BuilderException("Error creating document instance.  Cause: " + e, e);
		}
	}

	/**
	 * 构造器组件
	 * 
	 * @param validation
	 *            验证
	 * @param variables
	 *            变量
	 * @param entityResolver
	 *            实体解析器
	 */
	private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
		this.validation = validation;
		this.entityResolver = entityResolver;
		this.variables = variables;
		// 共通构造函数，除了把参数都设置到实例变量里面去以外，还初始化了XPath
		XPathFactory factory = XPathFactory.newInstance();
		this.xpath = factory.newXPath();
	}

}
